package org.springframework.security.web.authentication;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

/**
 * @author Dillon
 * @date 2024/7/8
 * @slogan 致敬大师 致敬未来的你
 * @desc 登录过滤器，基于用户名密码进行过滤
 */
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	/**
	 * 默认 用户名 key
	 */
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

	/**
	 * 默认 密码 key
	 */
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

	/**
	 * 定义登录接口匹配器
	 * login 作为默认登录拦截接口
	 * post 登录默认拦截方法
	 */
	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");

	/**
	 * 用户名 key
	 */
	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

	/**
	 * 密码 key
	 */
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

	/**
	 * 该请求路径是否仅支持POST
	 */
	private boolean postOnly = true;

	/**
	 * 构造函数
	 */
	public UsernamePasswordAuthenticationFilter() {
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
	}

	/**
	 * 构造函数
	 *
	 * @param authenticationManager 认证管理器
	 */
	public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
	}

	/**
	 * @param request  请求对象
	 * @param response 响应对象
	 * @return 是否认证成功
	 * @throws AuthenticationException 认证异常
	 */
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		// 是否只允许post判断
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("认证方法不支持: " + request.getMethod() + "方法");
		}
		// 用户名密码获取，并封装为 UsernamePasswordAuthenticationToken
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
		// 允许子类设置 detail
		setDetails(request, authRequest);
		// 委托认证实现类执行认证
		return this.getAuthenticationManager().authenticate(authRequest);
	}

	/**
	 * 获取密码的值
	 *
	 * @param request 请求对象
	 * @return 密码值
	 */
	@Nullable
	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(this.passwordParameter);
	}

	/**
	 * 获取用户名的值
	 *
	 * @param request 请求对象
	 * @return 用户名值
	 */
	@Nullable
	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(this.usernameParameter);
	}

	/**
	 * 提供默认的设置详情方法，并且允许子类覆盖该方法实现自定义的方法
	 *
	 * @param request     请求对象
	 * @param authRequest 认证管实现类
	 */
	protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
		authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * 设置用户名key
	 *
	 * @param usernameParameter key
	 */
	public void setUsernameParameter(String usernameParameter) {
		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
		this.usernameParameter = usernameParameter;
	}

	/**
	 * 设置密码 key
	 *
	 * @param passwordParameter key
	 */
	public void setPasswordParameter(String passwordParameter) {
		Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
		this.passwordParameter = passwordParameter;
	}

	/**
	 * 设置该请求路径是否仅支持POST
	 * false 标识该路径的其他请求方式 可作为一个默认的方法执行
	 *
	 * @param postOnly 是否仅支持POST
	 */
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	/**
	 * 获取用户名 key
	 *
	 * @return key
	 */
	public final String getUsernameParameter() {
		return this.usernameParameter;
	}

	/**
	 * 获取密码 key
	 *
	 * @return key
	 */
	public final String getPasswordParameter() {
		return this.passwordParameter;
	}

}
